﻿using System;
using System.IO;

namespace FMStudio.Documents
{
    /// <summary>
    /// Abstract base class for a IDocumentType implementation with a specific type of Document.
    /// </summary>
    /// <typeparam name="TDocument">The type of the document.</typeparam>
    public abstract class DocumentType<TDocument> : IDocumentType<TDocument> where TDocument : Document
    {
        #region Fields
        private string description;
        private string fileExtension;
        #endregion

        
        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentType{TDocument}" /> class.
        /// </summary>
        /// <param name="description">The description of the document type.</param>
        /// <param name="fileExtension">The file extension of the document type.</param>
        /// <exception cref="System.ArgumentException">Description must not be null or empty.</exception>
        /// <exception cref="System.ArgumentException">FileExtension must not be null or empty.</exception>
        /// <exception cref="System.ArgumentException">The argument fileExtension must start with the '.' character.</exception>
        protected DocumentType(string description, string fileExtension)
        {
            if (string.IsNullOrEmpty(description)) { throw new ArgumentException("Description must not be null or empty."); }
            if (string.IsNullOrEmpty(fileExtension)) { throw new ArgumentException("FileExtension must not be null or empty."); }
            if (fileExtension[0] != '.') { throw new ArgumentException("The argument fileExtension must start with the '.' character."); }

            this.description = description;
            this.fileExtension = fileExtension;
        }


        #region Properties
        /// <summary>
        /// The description of the document type.
        /// </summary>
        public string Description { get { return this.description; } }

        /// <summary>
        /// The file extension of the document type.
        /// </summary>
        public string FileExtension { get { return this.fileExtension; } }
        #endregion

        #region Methods
        #region Public Methods
        /// <summary>
        /// Determines whether this document type can create a new document.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this document type can; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanNew() { return false; }

        /// <summary>
        /// create a new document with a specified file path.
        /// </summary>
        /// <param name="filePath">The file path to create the document.</param>
        /// <returns>The document.</returns>
        public TDocument New(string filePath)
        {
            if (string.IsNullOrEmpty(filePath)) { throw new ArgumentException("filePath must not be null or empty."); }
            if (filePath.Substring(filePath.Length - FileExtension.Length).CompareTo(FileExtension) != 0)
                filePath = filePath + FileExtension;
            if (!Path.IsPathRooted(filePath)) { throw new ArgumentException("filePath"); }
            if (!CanNew()) { throw new NotSupportedException("The New operation is not supported. CanNew returned false."); }

            if (File.Exists(filePath)) { throw new ArgumentException("The file has already exist."); };

            TDocument document = NewCore(filePath);
            if (document != null)
            {
                document.FilePath = filePath;
                document.IsModified = true;
            }
            return document;
        }

        /// <summary>
        /// Determines whether this document type can open a document.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this document type can; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanOpen() { return false; }

        /// <summary>
        /// Opens a new document with a specified file path.
        /// </summary>
        /// <param name="filePath">The file path to open the document.</param>
        /// <returns>The document.</returns>
        public TDocument Open(string filePath)
        {
            if (string.IsNullOrEmpty(filePath)) { throw new ArgumentException("filePath must not be null or empty."); }
            if (!Path.IsPathRooted(filePath)) { throw new ArgumentException("filePath"); }
            if (!File.Exists(filePath)) { throw new FileNotFoundException("The file: " + filePath + " dose not exist."); };
            if (Path.GetExtension(filePath).ToUpper() != this.FileExtension.ToUpper()) 
            { throw new NotSupportedException("Unknown file extension, the Open operation failed."); }
            if (!CanOpen()) { throw new NotSupportedException("The Open operation is not supported. CanOpen returned false."); }

            TDocument document = OpenCore(filePath);
            if (document != null)
            {
                document.FilePath = filePath;
            }
            return document;
        }

        /// <summary>
        /// Determines whether this document type can save the specified docuemnt type.
        /// </summary>
        /// <param name="document">The docuemnt.</param>
        /// <returns>
        ///   <c>true</c> if this instance can save the specified docuemnt; otherwise, <c>false</c>.
        /// </returns>
        public bool CanSave(IDocument document) { return document is TDocument && CanSaveCore(); }

        /// <summary>
        /// Saves the specified document.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="filePath">The file path to save the document.</param>
        public void Save(TDocument document, string filePath)
        {
            if (document == null) { throw new ArgumentNullException("document"); }
            if (string.IsNullOrEmpty(filePath)) { throw new ArgumentException("filePath"); }
            if (filePath.Substring(filePath.Length - FileExtension.Length).CompareTo(FileExtension) != 0)
                filePath = filePath + FileExtension;
            if (!Path.IsPathRooted(filePath)) { throw new ArgumentException("filePath"); }
            if (!CanSave(document)) { throw new NotSupportedException("The Save operation is not supported. CanSave returned false."); }

            if (!document.ForceSave && !document.IsModified) return;

            SaveCore(document, filePath);

            if (CanOpen())
            {
                document.FilePath = filePath;
                document.IsModified = false;
            }
        }

        /// <summary>
        /// Saves the specified document.
        /// </summary>
        /// <param name="document">The document.</param>
        public void Save(TDocument document)
        {
            if (document == null) { throw new ArgumentNullException("document"); }

            Save(document, document.FilePath);
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// The core functionality create a new document.
        /// </summary>
        /// <param name="filePath">The file path to create the document.</param>
        /// <returns></returns>
        /// <exception cref="System.NotSupportedException"></exception>
        protected virtual TDocument NewCore(string filePath)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// The core functionality open a document.
        /// </summary>
        /// <param name="filePath">The file path of the specific the document.</param>
        /// <returns></returns>
        /// <exception cref="System.NotSupportedException"></exception>
        protected virtual TDocument OpenCore(string filePath)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Determines whether this document type can save a document.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this document type can; otherwise, <c>false</c>.
        /// </returns>
        protected virtual bool CanSaveCore() { return false; }

        /// <summary>
        /// The core functionality save a specific document.
        /// </summary>
        /// <param name="document">The specific document.</param>
        /// <param name="filePath">The file path to save the specific document.</param>
        /// <exception cref="System.NotSupportedException"></exception>
        protected virtual void SaveCore(TDocument document, string filePath)
        {
            throw new NotSupportedException();
        }
        #endregion
        #endregion
    }
}
